package com.enation.app.javashop.core.goods.service.impl;

import com.enation.app.javashop.core.base.CachePrefix;
import com.enation.app.javashop.core.goods.GoodsErrorCode;
import com.enation.app.javashop.core.goods.model.dos.QuantityLog;
import com.enation.app.javashop.core.goods.model.enums.QuantityLogType;
import com.enation.app.javashop.core.goods.model.vo.CacheGoods;
import com.enation.app.javashop.core.goods.model.vo.GoodsQuantityVO;
import com.enation.app.javashop.core.goods.model.vo.GoodsSkuVO;
import com.enation.app.javashop.core.goods.service.GoodsQuantityManager;
import com.enation.app.javashop.core.goods.service.GoodsQueryManager;
import com.enation.app.javashop.core.goods.service.GoodsSkuManager;
import com.enation.app.javashop.framework.cache.Cache;
import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.redis.transactional.RedisTransactional;
import com.enation.app.javashop.framework.util.DateUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Lock;

/**
 * 商品库存接口
 *
 * @author fk
 * @version v1.0 2017年4月1日 下午12:03:08
 */
@Service
public class GoodsQuantityManagerImpl implements GoodsQuantityManager {

    @Autowired
    private GoodsQueryManager goodsManager;
    @Autowired
    private GoodsSkuManager goodsSkuManager;

    @Autowired
    private DaoSupport goodsDaoSupport;
    @Autowired
    private Cache cache;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedissonClient redisson;

    private Lock getGoodsQuantityLock(Integer goodsId) {
        RLock lock = redisson.getLock("goods_quantity_lock_" + goodsId);
        return lock;
    }

    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public boolean addGoodsQuantity(String orderSn, GoodsQuantityVO goodsQuantity, QuantityLogType logType) {

        Lock lock = getGoodsQuantityLock(goodsQuantity.getGoodsId());
        lock.lock();

        if (!Thread.currentThread().isInterrupted()) {
            if (logger.isDebugEnabled()) {
                logger.debug(Thread.currentThread() + " locked goods[" + goodsQuantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
            }
            try {
                Integer enableQuantity = goodsQuantity.getEnableQuantity();
                Integer quantity = goodsQuantity.getQuantity();
                Integer goodsId = goodsQuantity.getGoodsId();
                Integer skuId = goodsQuantity.getSkuId();

                CacheGoods goods = goodsManager.getFromCache(goodsId);
                GoodsSkuVO skuVo = goodsSkuManager.getSkuFromCache(skuId);

                // 原库存
                Integer skuOldQuantity = skuVo.getQuantity();
                Integer goodsOldQuantity = goods.getQuantity();

                // 原可用库存
                Integer skuOldEnableQuantity = skuVo.getEnableQuantity();
                Integer goodsOldEnableQuantity = goods.getEnableQuantity();
                // 进行Sku库存增加计算
                skuOldQuantity = skuOldQuantity + quantity;
                skuOldEnableQuantity = skuOldEnableQuantity + enableQuantity;

                // 进行商品库存增加计算
                goodsOldQuantity = goodsOldQuantity + quantity;
                goodsOldEnableQuantity = goodsOldEnableQuantity + enableQuantity;

                skuVo.setQuantity(skuOldQuantity);
                skuVo.setEnableQuantity(skuOldEnableQuantity);

                goods.setQuantity(goodsOldQuantity);
                goods.setEnableQuantity(goodsOldEnableQuantity);

                // 更新goods中的skulist，以便更新缓存
                this.updateSkuVoList(goods, skuId, skuOldQuantity, skuOldEnableQuantity);

                // 更新缓存
                cache.put(CachePrefix.GOODS.getPrefix() + goodsId, goods);
                cache.put(CachePrefix.SKU.getPrefix() + skuId, skuVo);

                // 更新数据库
                this.goodsDaoSupport.execute("update es_goods set quantity=?, enable_quantity=? where goods_id=? ", goodsOldQuantity,
                        goodsOldEnableQuantity, goodsId);
                this.goodsDaoSupport.execute("update es_goods_sku set quantity = ? , enable_quantity = ? where sku_id=?",
                        skuOldQuantity, skuOldEnableQuantity, skuId);

                String reason = "";
                if (logType.equals(QuantityLogType.ORDER_CANCEL)) {
                    reason = "订单取消，增加库存";
                }

                if (logType.equals(QuantityLogType.RETURN)) {
                    reason = "退货入库";
                }

                this.log(orderSn, goodsQuantity, logType, OpType.ADD, reason);


                return true;
            } catch (Exception e) {
                if (logger.isErrorEnabled()) {
                    logger.error("增加库存失败:", e);
                }
            } finally {
                lock.unlock();
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread() + " unlocked [" + goodsQuantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
                }
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(Thread.currentThread() + " is interrupted");
            }
            return false;
        }

        return false;

    }


    @Override
    public boolean reduceGoodsQuantity(String orderSn, QuantityLogType logType, List<GoodsQuantityVO> goodsQuantityList) {

        //可能会回滚的列表
        List<GoodsQuantityVO> rollbackList = new ArrayList<>();

        for (GoodsQuantityVO quantity : goodsQuantityList) {


            Lock lock = getGoodsQuantityLock(quantity.getGoodsId());
            lock.lock();
            if (!Thread.currentThread().isInterrupted()) {
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread() + " locked goods[" + quantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
                }


                try {
                    boolean result = this.innerReduceGoodsQuantity(quantity);

                    //没有扣减成功,对缓存中的库存进行回滚
                    if (!result) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(Thread.currentThread() + " goods[" + quantity.getGoodsId() + "] 出库失败：" + rollbackList.size());
                        }
                        this.rollback(orderSn, rollbackList);
                        return false;
                    } else {
                        //加入可能要回滚的列表
                        rollbackList.add(quantity);
                        String reason = "创建订单扣减库存";
                        if (logType.equals(QuantityLogType.SHIP)) {
                            reason = "发货扣减库存";
                        }
                        //记录日志
                        this.log(orderSn, quantity, QuantityLogType.ORDER_CREATE, OpType.CUT, reason);
                    }
                } catch (Exception e) {
                    if (logger.isErrorEnabled()) {
                        logger.error("扣减库存失败:", e);
                    }
                } finally {
                    lock.unlock();
                    if (logger.isDebugEnabled()) {
                        logger.debug(Thread.currentThread() + " unlocked [" + quantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
                    }
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread() + " is interrupted");
                }
                return false;
            }
        }

        //清除回滚列表
        rollbackList.clear();

        return false;
    }


    /**
     * 回滚一个订单的库存
     *
     * @param orderSn 订单编号
     */
    @Override
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void rollbackReduce(String orderSn) {

        //查询扣减日志
        List<QuantityLog> logList = goodsDaoSupport.queryForList("select * from es_quantity_log where order_sn=? ", QuantityLog.class, orderSn);

        for (QuantityLog log : logList) {

            //缓存数据回滚
            //扣减库存时，库存是负数，所以要用0减，计算出要回滚的值
            GoodsQuantityVO goodsQuantity = new GoodsQuantityVO(log.getGoodsId(), log.getSkuId(), 0 - log.getQuantity(), 0 - log.getEnableQuantity());
            rollback(goodsQuantity);

            log(orderSn, goodsQuantity, QuantityLogType.ROLLBACK, OpType.ADD, "回滚库存");

        }

    }


    /**
     * 扣减商品库存
     *
     * @param goodsQuantity
     * @return
     */
    public boolean innerReduceGoodsQuantity(GoodsQuantityVO goodsQuantity) {

        // 要扣减的可用库存
        Integer enableQuantity = goodsQuantity.getEnableQuantity();
        Integer quantity = goodsQuantity.getQuantity();
        Integer goodsId = goodsQuantity.getGoodsId();
        Integer skuId = goodsQuantity.getSkuId();

        // 为空归零
        enableQuantity = enableQuantity == null ? 0 : enableQuantity;
        quantity = quantity == null ? 0 : quantity;

        CacheGoods goods = goodsManager.getFromCache(goodsId);
        GoodsSkuVO skuVo = goodsSkuManager.getSkuFromCache(skuId);


        // 原库存
        Integer skuOldQuantity = skuVo.getQuantity();
        Integer goodsOldQuantity = goods.getQuantity();

        // 原可用库存
        Integer skuOldEnableQuantity = skuVo.getEnableQuantity();
        Integer goodsOldEnableQuantity = goods.getEnableQuantity();

        goodsOldEnableQuantity = goodsOldEnableQuantity == null ? 0 : goodsOldEnableQuantity;
        goodsOldQuantity = goodsOldQuantity == null ? 0 : goodsOldQuantity;

        if (logger.isDebugEnabled()) {

            if (skuOldQuantity < quantity) {
                logger.debug(Thread.currentThread() + "出库失败 ： skuOldQuantity[" + skuOldQuantity + "] 小于 quantity:" + quantity);
            }


            if (skuOldEnableQuantity < enableQuantity) {
                logger.debug(Thread.currentThread() + "出库失败 ： skuOldEnableQuantity[" + skuOldEnableQuantity + "] 小于 enableQuantity:" + enableQuantity);
            }
        }

        // 库存和可用库存都充足
        if (skuOldQuantity >= quantity && skuOldEnableQuantity >= enableQuantity) {
            if (logger.isDebugEnabled()) {
                logger.debug(Thread.currentThread() + "->goods[" + goodsId + "]现库存为[" + goodsOldEnableQuantity + "] 更新为:" + (goodsOldEnableQuantity - enableQuantity));
            }
            // 进行Sku库存扣减计算
            skuOldQuantity = skuOldQuantity - quantity;
            skuOldEnableQuantity = skuOldEnableQuantity - enableQuantity;

            // 进行商品库存扣减计算
            goodsOldQuantity = goodsOldQuantity - quantity;
            goodsOldEnableQuantity = goodsOldEnableQuantity - enableQuantity;

            skuVo.setQuantity(skuOldQuantity);
            skuVo.setEnableQuantity(skuOldEnableQuantity);

            goods.setQuantity(goodsOldQuantity);
            goods.setEnableQuantity(goodsOldEnableQuantity);

            // 更新goods中的skulist，以便更新缓存
            this.updateSkuVoList(goods, skuId, skuOldQuantity, skuOldEnableQuantity);


            // 更新缓存
            cache.put(CachePrefix.GOODS.getPrefix() + goodsId, goods);
            cache.put(CachePrefix.SKU.getPrefix() + skuId, skuVo);

            // 更新数据库
            this.goodsDaoSupport.execute("update es_goods set quantity=?, enable_quantity=? where goods_id=? ",
                    goodsOldQuantity, goodsOldEnableQuantity, goodsId);
            this.goodsDaoSupport.execute("update es_goods_sku set quantity = ? , enable_quantity = ? where sku_id=?",
                    skuOldQuantity, skuOldEnableQuantity, skuId);


            return true;
        } else {
            return false;
        }
    }

    /**
     * 更新goods中的skuList 的库存
     *
     * @param goods             商品vo
     * @param skuId             要更新的skuid
     * @param skuQuantity       库存
     * @param skuEnableQuantity 可用库存
     */
    private void updateSkuVoList(CacheGoods goods, int skuId, int skuQuantity, int skuEnableQuantity) {

        // 对商品的skulist进行更新（这会对缓存进行更新）
        List<GoodsSkuVO> skuVoList = goods.getSkuList();
        for (GoodsSkuVO sku : skuVoList) {
            if (skuId == sku.getSkuId()) {
                sku.setQuantity(skuQuantity);
                sku.setEnableQuantity(skuEnableQuantity);
                break;
            }
        }

    }


    /**
     * 批量缓存回滚
     *
     * @param rollbackList
     */
    private void rollback(String orderSn, List<GoodsQuantityVO> rollbackList) {


        for (GoodsQuantityVO goodsQuantity : rollbackList) {
            rollback(goodsQuantity);
            log(orderSn, goodsQuantity, QuantityLogType.ROLLBACK, OpType.ADD, "回滚库存");
        }

    }

    /**
     * 对单个库存进行缓存回滚操作
     *
     * @param goodsQuantity
     */
    private void rollback(GoodsQuantityVO goodsQuantity) {

        Lock lock = getGoodsQuantityLock(goodsQuantity.getGoodsId());
        lock.lock();

        if (!Thread.currentThread().isInterrupted()) {
            if (logger.isDebugEnabled()) {
                logger.debug(Thread.currentThread() + " locked goods[" + goodsQuantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
            }

            try {
                Integer enableQuantity = goodsQuantity.getEnableQuantity();
                Integer quantity = goodsQuantity.getQuantity();
                Integer goodsId = goodsQuantity.getGoodsId();
                Integer skuId = goodsQuantity.getSkuId();


                CacheGoods goods = goodsManager.getFromCache(goodsId);
                GoodsSkuVO skuVo = goodsSkuManager.getSkuFromCache(skuId);

                // 原库存
                Integer skuOldQuantity = skuVo.getQuantity();
                Integer goodsOldQuantity = goods.getQuantity();

                // 原可用库存
                Integer skuOldEnableQuantity = skuVo.getEnableQuantity();
                Integer goodsOldEnableQuantity = goods.getEnableQuantity();


                // 进行Sku库存扣减计算
                skuOldQuantity = skuOldQuantity + quantity;
                skuOldEnableQuantity = skuOldEnableQuantity + enableQuantity;

                // 进行商品库存回滚计算
                goodsOldQuantity = goodsOldQuantity + quantity;
                goodsOldEnableQuantity = goodsOldEnableQuantity + enableQuantity;

                skuVo.setQuantity(skuOldQuantity);
                skuVo.setEnableQuantity(skuOldEnableQuantity);

                goods.setQuantity(goodsOldQuantity);
                goods.setEnableQuantity(goodsOldEnableQuantity);

                // 更新goods中的skulist，以便更新缓存
                this.updateSkuVoList(goods, skuId, skuOldQuantity, skuOldEnableQuantity);

                // 更新缓存
                cache.put(CachePrefix.GOODS.getPrefix() + goodsId, goods);
                cache.put(CachePrefix.SKU.getPrefix() + skuId, skuVo);


                //数据库回滚：对商品库存回滚
                this.goodsDaoSupport.execute("update es_goods set quantity=quantity+?, enable_quantity=enable_quantity+? where goods_id=? ", goodsQuantity.getQuantity(),
                        goodsQuantity.getEnableQuantity(), goodsQuantity.getGoodsId());


                //数据库回滚：对SKU库存回滚
                this.goodsDaoSupport.execute("update es_goods_sku set quantity = quantity+? , enable_quantity = enable_quantity+? where sku_id=?",
                        goodsQuantity.getQuantity(),
                        goodsQuantity.getEnableQuantity(), goodsQuantity.getSkuId());
            } catch (Exception e) {
                if (logger.isErrorEnabled()) {
                    logger.error("回滚库存失败:", e);
                }
            } finally {
                lock.unlock();
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread() + " unlocked [" + goodsQuantity.getGoodsId() + "] at " + DateUtil.toString(new Date(), "HH:MM:ss SS"));
                }
            }
        }


    }

    /**
     * 记录库存日志
     *
     * @param orderSn    订单编号
     * @param quantityVO 库存模型
     */
    private void log(String orderSn, GoodsQuantityVO quantityVO, QuantityLogType logType, OpType opType, String reason) {

        int enableQuantity = quantityVO.getEnableQuantity();
        int quantity = quantityVO.getQuantity();

        if (opType.equals(OpType.CUT)) {
            quantity = 0 - quantity;
            enableQuantity = 0 - enableQuantity;
        }

        QuantityLog quantityLog = new QuantityLog();
        quantityLog.setOrderSn(orderSn);
        quantityLog.setGoodsId(quantityVO.getGoodsId());
        quantityLog.setSkuId(quantityVO.getSkuId());
        quantityLog.setQuantity(quantity);
        quantityLog.setEnableQuantity(enableQuantity);
        quantityLog.setOpTime(DateUtil.getDateline());
        quantityLog.setLogType(logType.name());
        quantityLog.setReason(reason);
        goodsDaoSupport.insert(quantityLog);

    }

    @Override
    @RedisTransactional(lockName = "goods_quantity_lock")
    @Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void updateGoodsQuantity(List<GoodsQuantityVO> goodsQuantityList) {
        if (goodsQuantityList == null || goodsQuantityList.size() == 0) {
            throw new ServiceException(GoodsErrorCode.E301.name(), "参数错误");
        }
        Integer quantity = 0;
        Integer enableQuantity = 0;
        // 先把库存信息存到库中
        for (GoodsQuantityVO quantityVo : goodsQuantityList) {
            // 更新货品库存
            this.goodsDaoSupport.execute("update es_goods_sku set quantity=?,enable_quantity=? where sku_id=? ",
                    quantityVo.getQuantity(), quantityVo.getEnableQuantity(), quantityVo.getSkuId());
            quantity = quantityVo.getQuantity() + quantity;
            enableQuantity = quantityVo.getEnableQuantity() + enableQuantity;
            // 更新sku缓存
            GoodsSkuVO skuVo = goodsSkuManager.getSkuFromCache(quantityVo.getSkuId());
            skuVo.setQuantity(quantityVo.getQuantity());
            skuVo.setEnableQuantity(quantityVo.getEnableQuantity());
            cache.put(CachePrefix.SKU.getPrefix() + quantityVo.getSkuId(), skuVo);
        }
        // 把累加的库存和可用库存保存到商品表,并更新商品缓存
        this.goodsDaoSupport.execute("update es_goods set quantity=?, enable_quantity=? where goods_id=? ", quantity,
                enableQuantity, goodsQuantityList.get(0).getGoodsId());
        cache.remove(CachePrefix.GOODS.getPrefix() + goodsQuantityList.get(0).getGoodsId());
    }
}

/**
 * 库存日志操作类型
 */
enum OpType {

    /**
     * 增加
     */
    ADD,

    /**
     * 扣减
     */
    CUT;

}